/*******************************************************************************
 * Copyright (c) 2011, 2016 Eurotech and/or its affiliates
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Eurotech
 *******************************************************************************/
package org.eclipse.kura.emulator.watchdog;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.kura.configuration.ConfigurableComponent;
import org.eclipse.kura.watchdog.CriticalComponent;
import org.eclipse.kura.watchdog.WatchdogService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WatchdogServiceImpl implements WatchdogService, ConfigurableComponent {

    private static final Logger s_logger = LoggerFactory.getLogger(WatchdogServiceImpl.class);

    private Map<String, Object> m_properties;
    private ScheduledExecutorService m_executor;
    private ScheduledFuture<?> m_future;
    private int pingInterval = 10000;	// milliseconds
    private static ArrayList<CriticalServiceImpl> s_criticalServiceList;
    private boolean m_configEnabled = false;	// initialized in properties, if false -> no watchdog
    private boolean m_enabled;

    protected void activate(ComponentContext componentContext, Map<String, Object> properties) {
        this.m_properties = properties;
        if (properties == null) {
            s_logger.debug("activating WatchdogService with null props");
        } else {
            s_logger.debug("activating WatchdogService with {}", properties.toString());
        }
        s_criticalServiceList = new ArrayList<CriticalServiceImpl>();
        this.m_enabled = false;

        // clean up if this is not our first run
        if (this.m_executor != null) {
            this.m_executor.shutdown();
            while (!this.m_executor.isTerminated()) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.m_executor = null;
        }

        this.m_executor = Executors.newSingleThreadScheduledExecutor();

        this.m_future = this.m_executor.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                Thread.currentThread().setName(getClass().getSimpleName());
                if (WatchdogServiceImpl.this.m_configEnabled) {
                    doWatchdogLoop();
                }
            }
        }, 0, this.pingInterval, TimeUnit.MILLISECONDS);
    }

    protected void deactivate(ComponentContext componentContext) {
        this.m_executor.shutdown();
        while (!this.m_executor.isTerminated()) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.m_executor = null;
        s_criticalServiceList = null;
    }

    public void updated(Map<String, Object> properties) {
        s_logger.debug("updated...");
        this.m_properties = properties;
        if (this.m_properties != null) {
            if (this.m_properties.get("enabled") != null) {
                if (this.m_properties.get("enabled") != null) {
                    this.m_configEnabled = (Boolean) this.m_properties.get("enabled");
                }
            }
            if (!this.m_configEnabled) {
                return;
            }
            if (this.m_properties.get("pingInterval") != null) {
                this.pingInterval = (Integer) this.m_properties.get("pingInterval");
                if (this.m_future != null) {
                    this.m_future.cancel(false);
                    while (!this.m_future.isDone()) {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                this.m_future = this.m_executor.scheduleAtFixedRate(new Runnable() {

                    @Override
                    public void run() {
                        Thread.currentThread().setName(getClass().getSimpleName());
                        if (WatchdogServiceImpl.this.m_configEnabled) {
                            doWatchdogLoop();
                        }
                    }
                }, 0, this.pingInterval, TimeUnit.MILLISECONDS);
            }
        }
    }

    @Override
    public void startWatchdog() {
        this.m_enabled = true;
    }

    @Override
    public void stopWatchdog() {
        this.m_enabled = false;
    }

    @Override
    public int getHardwareTimeout() {
        return 0;
    }

    @Override
    public void registerCriticalComponent(CriticalComponent criticalComponent) {
        final CriticalServiceImpl service = new CriticalServiceImpl(criticalComponent.getCriticalComponentName(),
                criticalComponent.getCriticalComponentTimeout());
        synchronized (s_criticalServiceList) {
            // avoid to add same component twice (eg in case of a package updating)
            boolean existing = false;
            for (CriticalServiceImpl csi : s_criticalServiceList) {
                if (criticalComponent.getCriticalComponentName().compareTo(csi.getName()) == 0) {
                    existing = true;
                }
            }
            if (!existing) {
                s_criticalServiceList.add(service);
            }
        }

        s_logger.debug("Added " + criticalComponent.getCriticalComponentName() + ", with timeout = "
                + criticalComponent.getCriticalComponentTimeout() + ", list contains " + s_criticalServiceList.size()
                + " critical services");
    }

    @Override
    @Deprecated
    public void registerCriticalService(CriticalComponent criticalComponent) {
        registerCriticalComponent(criticalComponent);
    }

    @Override
    public void unregisterCriticalComponent(CriticalComponent criticalComponent) {
        synchronized (s_criticalServiceList) {
            for (int i = 0; i < s_criticalServiceList.size(); i++) {
                if (criticalComponent.getCriticalComponentName()
                        .compareTo(s_criticalServiceList.get(i).getName()) == 0) {
                    s_criticalServiceList.remove(i);
                    s_logger.debug("Critical service " + criticalComponent.getCriticalComponentName() + " removed, "
                            + System.currentTimeMillis());
                }
            }
        }
    }

    @Override
    public void unregisterCriticalService(CriticalComponent criticalComponent) {
        unregisterCriticalComponent(criticalComponent);
    }

    @Override
    public List<CriticalComponent> getCriticalComponents() {
        return null;
    }

    @Override
    public void checkin(CriticalComponent criticalService) {
        synchronized (s_criticalServiceList) {
            for (CriticalServiceImpl csi : s_criticalServiceList) {
                if (criticalService.getCriticalComponentName().compareTo(csi.getName()) == 0) {
                    csi.update();
                }
            }
        }
    }

    private void doWatchdogLoop() {
        if (!this.m_enabled) {
            return;
        }

        boolean failure = false;
        // Critical Services
        synchronized (s_criticalServiceList) {
            if (!s_criticalServiceList.isEmpty()) {
                for (CriticalServiceImpl csi : s_criticalServiceList) {
                    if (csi.isTimedOut()) {
                        failure = true;
                        s_logger.warn("Critical service " + csi.getName() + " failed -> SYSTEM REBOOT");
                    }
                }
            }
        }
        if (!failure) {
            refresh_watchdog();
        }
    }

    private void refresh_watchdog() {
        File f = new File("/dev/watchdog");
        if (f.exists()) {
            try {
                FileOutputStream fos = new FileOutputStream(f);
                PrintWriter pw = new PrintWriter(fos);
                pw.write("w");
                pw.flush();
                fos.getFD().sync();
                pw.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public boolean isConfigEnabled() {
        return this.m_configEnabled;
    }

    public void setConfigEnabled(boolean configEnabled) {
        this.m_configEnabled = configEnabled;
    }
}
